#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr_pools.h>

#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_path.h"
#include "svn_io.h"
#include "svn_delta.h"
#include "svn_config.h"
#include "svn_props.h"
#include "svn_base64.h"

#include "com_googlecode_svnj_NativeEditor.h"
#include "jniutil.h"

static jfieldID fid_ptr;
static apr_pool_t *master_pool;

/* initialize field ID's */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_initFields
(JNIEnv *env, jclass clazz) {
  fid_ptr = (*env)->GetFieldID(env, clazz, "ptr", "J");
  apr_initialize();
  master_pool = svn_pool_create(NULL);
}

typedef struct {
  const svn_delta_editor_t *editor;
  void *baton, *root_baton, *file_baton;
  apr_array_header_t *dir_batons;
  apr_pool_t *pool;
  /* txdelta window handler protocol */
  svn_txdelta_window_handler_t *window_handler;
  void *window_handler_baton;
  /* handle base64 encoded svndiff data */
  svn_stream_t *base64_decoder;
} native_editor;

/* extern linkage to libsvn_delta/debug_editor.c */
extern svn_error_t *
svn_delta__get_debug_editor(const svn_delta_editor_t **editor,
                            void **edit_baton,
                            const svn_delta_editor_t *wrapped_editor,
                            void *wrapped_baton,
                            apr_pool_t *pool);
/* our own debug editor */
extern svn_error_t *
init_svnj_noop_editor(const svn_delta_editor_t **editor,
                      void **edit_baton,
                      const char *path,
                      apr_pool_t *pool);

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_initDebug
(JNIEnv *env, jobject self) {
  apr_pool_t *pool;
  native_editor *ned;
  
  pool = svn_pool_create(master_pool);
  ned = apr_pcalloc(pool, sizeof(native_editor));
  ned->pool = pool;
  ned->dir_batons = apr_array_make(ned->pool, 10, sizeof(void*));
  SVN_JNI_ERR(
    init_svnj_noop_editor(&(ned->editor), &(ned->baton), "", ned->pool)
    ,
    );
  (*env)->SetLongField(env, self, fid_ptr, (jlong)ned);
}

/* boilerplate initialization when starting a JNI call */
#define PTR_INIT() svn_error_t *err; native_editor *ned;  \
  do { jlong ptr = (*env)->GetLongField(env, self, fid_ptr); \
    CHECK_PTR(ptr, ); \
    ned = (native_editor *)ptr; \
} while (0)

/* boilerplate checking an SVN error, throw java exception as needed */
#define ERR_CHECK()  do { if (err != SVN_NO_ERROR) \
    { throw_svn_exception(env, err, TRUE); } } while (0)
/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    abortEdit
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_abortEdit
(JNIEnv *env, jobject self) {
  PTR_INIT();
  err = ned->editor->abort_edit(ned->baton, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    absentDirectory
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_absentDirectory
(JNIEnv *env, jobject self, jstring jpath) {
  const char* path;
  PTR_INIT();
  path = make_cstring(env, jpath, ned->pool);
  err = ned->editor->absent_directory(path, ned->baton, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    absentFile
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_absentFile
(JNIEnv *env, jobject self, jstring jpath) {
  const char* path;
  void *parent_baton;
  PTR_INIT();
  path = make_cstring(env, jpath, ned->pool);
  parent_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  err = ned->editor->absent_file(path, parent_baton, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    addDirectory
 * Signature: (Ljava/lang/String;Ljava/lang/String;J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_addDirectory
(JNIEnv *env, jobject self, jstring jpath, jstring jcopyfrom_path, jlong jcopyfrom_rev) {
  void *parent_baton, *dir_baton;
  const char* path, *copyfrom_path;
  svn_revnum_t copyfrom_rev;
  PTR_INIT();
  parent_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  path = make_cstring(env, jpath, ned->pool);
  copyfrom_path = make_cstring(env, jcopyfrom_path, ned->pool);
  copyfrom_rev = (svn_revnum_t) jcopyfrom_rev;
  err = ned->editor->add_directory(path, parent_baton, copyfrom_path, copyfrom_rev,
                                   ned->pool, &dir_baton);
  ERR_CHECK();
  *(const void**)apr_array_push(ned->dir_batons) = dir_baton;
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    addFile
 * Signature: (Ljava/lang/String;Ljava/lang/String;J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_addFile
(JNIEnv *env, jobject self, jstring jpath, jstring jcopyfrom_path, jlong copyfrom_rev) {
  void *parent_baton;
  const char *path, *copyfrom_path;
  PTR_INIT();
  parent_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  path = make_cstring(env, jpath, ned->pool);
  copyfrom_path = make_cstring(env, jcopyfrom_path, ned->pool);
  err = ned->editor->add_file(path, parent_baton, copyfrom_path,
                              (svn_revnum_t)copyfrom_rev,
                              ned->pool, &(ned->file_baton));
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    appendTextDelta
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_appendTextDelta
(JNIEnv *env, jobject self, jstring jdelta) {
  const char *data;
  apr_size_t data_len, nwritten;
  PTR_INIT();
  data = make_cstring(env, jdelta, ned->pool);
  nwritten = data_len = (*env)->GetStringLength(env, jdelta);
  err = svn_stream_write(ned->base64_decoder, data, &nwritten);
  ERR_CHECK();
  if (nwritten != data_len) {
    throw_exception(env, "java/lang/RuntimeException",
                    "underlying stream write length error");
  }
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    beginTextDelta
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_beginTextDelta
(JNIEnv *env, jobject self, jstring jchecksum) {
  const char* checksum;
  svn_stream_t *svndiff_decoder;
  PTR_INIT();
  checksum = make_cstring(env, jchecksum, ned->pool);
  err = ned->editor->apply_textdelta(ned->file_baton,
                                     checksum,
                                     ned->pool,
                                     &(ned->window_handler),
                                     &(ned->window_handler_baton)
    );
  ERR_CHECK();
  svndiff_decoder  = svn_txdelta_parse_svndiff(ned->window_handler,
                                               ned->window_handler_baton,
                                               TRUE,
                                               ned->pool);
  ned->base64_decoder = svn_base64_decode(svndiff_decoder, ned->pool);
  
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    endTextDelta
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_endTextDelta
(JNIEnv *env, jobject self) {
  PTR_INIT();
  err = svn_stream_close(ned->base64_decoder);
  ned->window_handler = NULL;
  ned->window_handler_baton = NULL;
  ned->base64_decoder = NULL;
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    changeDirProp
 * Signature: (Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_changeDirProp
(JNIEnv *env, jobject self, jstring jname, jstring jvalue) {
  const char *name;
  const svn_string_t *value;
  void *dir_baton;

  PTR_INIT();
  dir_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  name = make_cstring(env, jname, ned->pool);
  value = make_cstring2(env, jvalue, ned->pool);
  err = ned->editor->change_dir_prop(dir_baton, name, value, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    changeFileProp
 * Signature: (Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_changeFileProp
(JNIEnv *env, jobject self, jstring jname, jstring jvalue) {
  const char *name;
  const svn_string_t *value;

  PTR_INIT();
  name = make_cstring(env, jname, ned->pool);
  value = make_cstring2(env, jvalue, ned->pool);
  err = ned->editor->change_file_prop(ned->file_baton, name, value, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    closeDirectory
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_closeDirectory
(JNIEnv *env, jobject self) {
  void *dir_baton;
  PTR_INIT();
  dir_baton = apr_array_pop(ned->dir_batons);
  err = ned->editor->close_directory(dir_baton, ned->pool);
  ERR_CHECK();
}
/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    closeEdit
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_closeEdit
(JNIEnv *env, jobject self) {
  PTR_INIT();
  err = ned->editor->close_edit(ned->baton, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    closeFile
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_closeFile
(JNIEnv *env, jobject self, jstring jchecksum) {
  const char* checksum;
  PTR_INIT();
  checksum = make_cstring(env, jchecksum, ned->pool);
  err = ned->editor->close_file(ned->file_baton, checksum, ned->pool);
  ned->file_baton = NULL;
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    deleteEntry
 * Signature: (Ljava/lang/String;J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_deleteEntry
(JNIEnv *env, jobject self, jstring jpath, jlong rev) {
  void *parent_baton;
  const char *path;
  PTR_INIT();
  parent_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  path = make_cstring(env, jpath, ned->pool);
  err = ned->editor->delete_entry(path, (svn_revnum_t)rev, parent_baton, ned->pool);
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    openDirectory
 * Signature: (Ljava/lang/String;J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_openDirectory
(JNIEnv *env, jobject self, jstring jpath, jlong rev) {
  void *dir_baton, *parent_baton;
  const char *path;
  PTR_INIT();
  parent_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  path = make_cstring(env,jpath,ned->pool);
  err = ned->editor->open_directory(path, parent_baton, (svn_revnum_t)rev,
                                    ned->pool, &dir_baton);
  ERR_CHECK();
  *(const void**)apr_array_push(ned->dir_batons) = dir_baton;
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    openFile
 * Signature: (Ljava/lang/String;J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_openFile
(JNIEnv *env, jobject self, jstring jpath, jlong jbase_rev) {
  void *parent_baton;
  const char *path;
  PTR_INIT();
  parent_baton = APR_ARRAY_IDX(ned->dir_batons, ned->dir_batons->nelts - 1, void*);
  path = make_cstring(env, jpath, ned->pool);
  err = ned->editor->open_file(path, parent_baton, (svn_revnum_t)jbase_rev,
                               ned->pool, &(ned->file_baton));
  ERR_CHECK();
}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    openRoot
 * Signature: (J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_openRoot
(JNIEnv *env, jobject self, jlong rev) {
  void *root_baton;
  PTR_INIT();
  err = ned->editor->open_root(ned->baton, (svn_revnum_t)rev, ned->pool, &(root_baton));
  ERR_CHECK();
  *(const void**)apr_array_push(ned->dir_batons) = root_baton;
  /*fprintf(stderr, "[native.openRoot(): root_baton 0x%08x nelts=%ld\n",
    (unsigned int)root_baton, ned->dir_batons->nelts);*/

}

/*
 * Class:     com_googlecode_svnj_NativeEditor
 * Method:    setTargetRevision
 * Signature: (J)V
 */
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_NativeEditor_setTargetRevision
(JNIEnv *env, jobject self, jlong rev) {
  PTR_INIT();
  err = ned->editor->set_target_revision(ned->baton, (svn_revnum_t)rev, ned->pool);
  ERR_CHECK();
}

